/* 
 * File:   BlinnPhongShader.cpp
 * Author: brady
 * 
 * Created on February 8, 2011, 11:10 AM
 */

#include <vector>

#include "BlinnPhongShader.h"

BlinnPhongShader::BlinnPhongShader() {
    BlinnPhongShader(0.5, 0.5, 0.5, 1.0, 1.0, 1.0);
}

BlinnPhongShader::BlinnPhongShader(const BlinnPhongShader& orig)
{
        this->AmbientCoefficient = orig.AmbientCoefficient;
        this->Color = orig.Color;
        this->MirrorCoefficient = orig.MirrorCoefficient;
        this->PhongExponent = orig.PhongExponent;
        this->SpecularColor = orig.SpecularColor;
        this->recursionDepth = orig.recursionDepth;
        this->GlossValue = orig.GlossValue;
        this->texture = orig.texture;
}



BlinnPhongShader::BlinnPhongShader(double r, double g, double b, double sr, double sg, double sb)
{
    Color = Vector3D(r, g, b);
    SpecularColor = Vector3D(sr, sg, sb);
    AmbientCoefficient = 0.2;
    PhongExponent = 25.0;
    MirrorCoefficient = 0.0;
    recursionDepth = 5;
    GlossValue = 0.0;
}

BlinnPhongShader::~BlinnPhongShader() {
}

Vector3D BlinnPhongShader::Reflect(Vector3D &normal, Ray& hitRay, int spp)
{
    Vector3D viewDir(hitRay.GetDirection());
    Vector3D reflected;
    reflected = viewDir * -1.0 + (normal * (viewDir.dot(normal) * 2.0));
    reflected.normalize();

    //for glossy reflections

    if (this->GlossValue > 0.0) {
        Vector3D Origin = reflected + hitRay.GetOrigin();
        Vector3D Gaze, U, V, W;
        Gaze =  hitRay.GetOrigin() - Origin;
        Gaze.normalize();
        Vector3D up = Vector3D(0.0, 1.0, 0.0);
        W = Gaze * -1.0;
        U = up.cross(W);
        U.normalize();
        V = W.cross(U);
        V.normalize();


        double left, right, top, bottom;
        left = -GlossValue / 2;
        right = GlossValue / 2;
        top = -GlossValue / 2;
        bottom = GlossValue / 2;
        double xOff, yOff;
        double sampleXOffset, sampleYOffset;
        int sampleNum = hitRay.sNum;
        int sampleColumn;
        int sampleRow;
        int sampleRows = sqrt(spp);
        int sampleCols = sampleRows;
        if (sampleNum < 0) {
            sampleXOffset = (sampleNum % 2 == 0) ? 0.0 : 1.0;
            sampleYOffset = (sampleNum <= -3) ? 0.0 : 1.0;
            sampleColumn = sampleXOffset * (sampleCols - 1);
            sampleRow = sampleYOffset * (sampleRows - 1);
            if (sampleNum == -5) {
                sampleXOffset = 0.5;
                sampleYOffset = 0.5;
                sampleRow = sampleRows / 2 - 1;
                sampleColumn = sampleCols / 2 - 1;
            }
        } else {
            double rMult = ((double) random() / (double) RAND_MAX);
            sampleXOffset = (1.0 / (sampleCols)) * rMult;
            rMult = ((double) random() / (double) RAND_MAX);
            sampleYOffset = (1.0 / (sampleRows)) * rMult;

            sampleColumn = sampleNum % sampleCols;
            sampleRow = sampleNum / sampleRows;

            sampleXOffset += (double) sampleColumn / (double) sampleCols;
            sampleYOffset += (double) sampleRow / (double) sampleRows;
        }

        xOff = left + GlossValue * ((sampleColumn + sampleXOffset) / sampleCols);
        yOff = top + GlossValue * ((sampleRow + sampleYOffset) / sampleRows);
        Vector3D rPoint;
        rPoint = Origin + U * xOff + V*yOff;
        return rPoint - hitRay.GetOrigin();
    }
    return reflected;
}

Vector3D BlinnPhongShader::Bisect(Vector3D &v1, Vector3D &v2)
{
    Vector3D bisector(v1);
    bisector += v2;
    bisector.normalize();
    return bisector;
}


Vector3D BlinnPhongShader::ComputeHighlights(Ray& hitRay, Vector3D &normal, Scene* scene)
{
    Vector3D point(hitRay.GetOrigin());
    Vector3D viewDir(hitRay.GetDirection());

    Vector3D highlights = Vector3D(0.0, 0.0, 0.0);
    int lightCount = scene->lights.size();
    for(int i = 0; i < lightCount; i++){
        Light* curLight = scene->lights[i];
        Vector3D toLight = curLight->GetOrigin() - point;
        toLight.normalize();
        double highlightCoeff;
        Vector3D h = Bisect(viewDir, toLight);
        double c = h.dot(normal);
        highlightCoeff = pow(c, PhongExponent);
        highlights += Mix(curLight->GetColor() * highlightCoeff, SpecularColor);
        }
    return highlights;

}

Vector3D BlinnPhongShader::ComputeReflections(Ray &hitRay, Vector3D &normal, Scene* scene)
{
    Vector3D point = hitRay.GetOrigin();

    Vector3D reflection = Vector3D(0.0, 0.0, 0.0);
    if (MirrorCoefficient > 0.0) {
        Ray reflectedRay = Ray(point, Reflect(normal, hitRay, scene->SamplesPerPixel));
        reflectedRay.SetRecursionDepth(hitRay.GetRecursionDepth() - 1);
        reflectedRay.RefractiveIndex = hitRay.RefractiveIndex;
        reflectedRay.sNum = hitRay.sNum;
        if (reflectedRay.GetRecursionDepth() < 0) {
            return scene->BGColor;
        }
        HitStruct hs;
        bool hit = scene->BVHRoot->TestIntersect(reflectedRay, scene->Tmin, scene->Tmax, hs);
        Shader* mirroredShader = hs.shader;
        if (hit) {
            reflection = mirroredShader->Shade(reflectedRay, hs, scene);
        } else {
            reflection = scene->BGColor;
        }
    }
    return reflection;
}

Vector3D BlinnPhongShader::Shade(Ray &viewingRay, HitStruct& hitStruct, Scene* scene)
{
    Vector3D shadedLight = Vector3D(0.0, 0.0, 0.0);
    Vector3D highLight = Vector3D(0.0, 0.0, 0.0);
    Vector3D reflectedLight = Vector3D(0.0, 0.0, 0.0);
    Vector3D view = viewingRay.GetDirection() * -1.0;
    view.normalize();
    Vector3D hitpoint = viewingRay.Follow(hitStruct.tVal);
    int rdepth = viewingRay.GetRecursionDepth();
    //hitRay is the ray originating at the point of intersection
    //with direction pointing back to the origin of the viewingRay
    Ray hitRay(hitpoint, view);
    hitRay.SetRecursionDepth(rdepth);
    hitRay.RefractiveIndex = viewingRay.RefractiveIndex;
    hitRay.sNum = viewingRay.sNum;
    Vector3D surfaceNormal = hitStruct.normal;
    //offset by texture bump value
    Vector3D displacement = this->texture->GetNormalOffsetAt(hitRay.GetOrigin());
    surfaceNormal += displacement;
    surfaceNormal.normalize();


    shadedLight = ComputeLighting(hitRay, surfaceNormal, scene);
    highLight = ComputeHighlights(hitRay, surfaceNormal, scene);
    reflectedLight = ComputeReflections(hitRay, surfaceNormal, scene);
    Vector3D finalColor;
    Vector3D pointColor = this->texture->GetColorAt(hitRay.GetOrigin());
    finalColor += Mix(pointColor, shadedLight) * (1-AmbientCoefficient) + (pointColor * AmbientCoefficient);
    //this would be our color without highlights or reflections
    finalColor = finalColor * (1.0 - MirrorCoefficient) + reflectedLight * MirrorCoefficient;
    //now it has color mixed with reflections
    finalColor = finalColor + highLight;
    //and now highlights are added
    finalColor = Cap(finalColor, 1.0);
    return finalColor;
}


